#!/usr/bin/lua

-----------------------------------------------------------------------------
-- USS to Finite State Machine Prototype
-- Nash State Machine Lib
-- Author: Nash Tsai
-- RCS ID: $Id: uss2FSM.lua,v 1.0.1 2007/07/25 $
-----------------------------------------------------------------------------

-----------------------------------------------------------------------------
-- Declare module and import dependencies
-----------------------------------------------------------------------------
local _G = _G
local string = require("string")
local table = require("table")
local os = require("os")

---------------------------------------------------------------------------
-- Parse Functions
-----------------------------------------------------------------------------
local parseTitle = function()
    g_stateMachine.title = io.read()
    return true,""
end 

local parseType = function()

    local line = io.read()
    typeParse = typeParsers[line]
    
    if (nil ~= typeParse) then
        return true, typeParse()
    end
    return true,""
end -- parseType

local parseLink = function()

    g_stateMachine.links = g_stateMachine.links or {}

    -- skip to objId:
    local tag = io.read()
    while tag and tag ~= "objId:" do
        tag = io.read()
    end
    assert( tag == "objId:" )
    local id = io.read()

    -- create a table to hold the link details.
    g_stateMachine.links[ id ] = {}

    -- skip to objStartId:
    while tag and tag ~= "objStartId:" do
        tag = io.read()
    end
    assert( tag == "objStartId:" )
    g_stateMachine.links[id].startId = io.read()

    -- skip to objEndId:
    while tag and tag ~= "objEndId:" do
        tag = io.read()
    end
    assert( tag == "objEndId:" )
    g_stateMachine.links[id].endId = io.read()   

    -- skip to eventName:
    while tag and tag ~= "eventName:" do
        tag = io.read()
    end
    assert( tag == "eventName:" )
    g_stateMachine.links[id].name = io.read()
    
    -- skip to eventCond:
    while tag and tag ~= "eventCond:" do
        tag = io.read()
    end
    assert( tag == "eventCond:" )
    g_stateMachine.links[id].condition = io.read()
   

    -- skip to eventAction:
    while tag and tag ~= "eventAction:" do
        tag = io.read()
    end
    assert( tag == "eventAction:" )
    g_stateMachine.links[id].action = io.read()

    -- skip to type:, which indicates the start of a new section.
    while tag and tag ~= "type:" do
        tag = io.read()
    end
    return tag
end -- parseLink

local parseState = function()

    g_stateMachine.states = g_stateMachine.states or {}

    --skip to objId:
    local tag = io.read()
    while tag and tag ~= "objId:" do
        tag = io.read()
    end
    assert( tag == "objId:" )
    local id = io.read()
    g_stateMachine.states[ id ] = {}

    g_stateMachine.states[id].links = {}
    
    tag = io.read()
    while tag and tag ~= "Num.Links:" do
        tag = io.read()
    end
    local numLinks = tonumber( io.read() )
    
    if numLinks > 0 then
        --skip to link objId:
        tag = io.read()
        while tag and tag ~= "link objId:" do
            tag = io.read()
        end
        
        while tag and tag == "link objId:" do
            local linkId = io.read()
            g_stateMachine.states[id].links[linkId] = true
            tag = io.read()
        end
    end
    
    while tag and tag ~= "stateName:" do
        tag = io.read()
    end
    assert( tag == "stateName:" )
    local name = io.read()
    g_stateMachine.states[id].name = name
    
    while tag and tag ~= "activitiesLen:" do
        tag = io.read()
    end
    assert( tag == "activitiesLen:" )
    local activitesLen = tonumber(io.read())
    while tag and tag ~= "activities:" do
        tag = io.read()
    end
    assert( tag == "activities:" )
    local activities = io.read(activitesLen)

    --append a new line, if needed, to make parsing of activites easier later.
    if not string.find(activities,"\n$") then
        activities = activities .. "\n"
    end

    --extract from the activities field all the specific code and variables
    --that makes this state machine unique or provides info to the state machine driver
    --(like current animation, etc)
    parseActivities( g_stateMachine.states[id], activities )
    
    -- skip to type:, which indicates the start of a new section.
    while tag and tag ~= "type:" do
        tag = io.read()
    end
    return tag
end -- parseState

parseActivities = function( stateData, activities )

    --bring the tables into existance (if they don't)
    stateData.functions = stateData.functions or {}
    stateData.variables = stateData.variables or {}

    local funcName = nil
     
    for line in string.gfind(activities, "(.-)\n") do
        --is this a function
        if string.find(line, "OnEnter:") == 1 or
           string.find(line, "OnExit:") == 1 or
           string.find(line, "OnUpdate:") == 1 then
            local rest
            _, _, funcName, rest = string.find(line, "^(.-):(.*)$")
            assert( funcName and string.len(funcName)>=1, "Can't extract function name from " .. line )
            stateData.functions[funcName] = rest or ""
        --or a continuation of a function?
        elseif string.find(line, "%s") == 1 then
            assert( funcName, line .. " appears to be a continuation of a function, but there is no current function." )
            stateData.functions[funcName] = stateData.functions[funcName] .. line
        else
            -- it must be a variable assignment...
            funcName = nil
            local _, _, name, value = string.find(line, "^(.-)=(.*)$")
            if name and value then
                stateData.variables[name] = value
            end
        end
    end
end --parseActivities


local parseStart = function()

    assert( not g_stateMachine.start ) --only one start per machine

    tag = io.read()
    while tag and tag ~= "Num.Links:" do
        tag = io.read()
    end
    local numLinks = tonumber( io.read() )
    assert( numLinks == 1 )
    
    --skip to link objId:
    tag = io.read()
    while tag and tag ~= "link objId:" do
        tag = io.read()
    end
        
    while tag and tag == "link objId:" do
        local linkId = io.read()
        g_stateMachine.startLink = linkId
        tag = io.read()
    end
    
    -- skip to type:, which indicates the start of a new section.
    while tag and tag ~= "type:" do
        tag = io.read()
    end
    return tag
end -- parseStart


local parseEnd = function()

    assert( not g_stateMachine.endId ) --only one end per machine

    --skip to objId:
    local tag = io.read()
    while tag and tag ~= "objId:" do
        tag = io.read()
    end
    assert( tag == "objId:" )
    g_stateMachine.endId = io.read()
    
    tag = io.read()
    while tag and tag ~= "Num.Links:" do
        tag = io.read()
    end
    local numLinks = tonumber( io.read() )
    assert( numLinks == 1 )
    
    --skip to link objId:
    tag = io.read()
    while tag and tag ~= "link objId:" do
        tag = io.read()
    end
        
    while tag and tag == "link objId:" do
        local linkId = io.read()
        tag = io.read()
    end
    
    -- skip to type:, which indicates the start of a new section.
    while tag and tag ~= "type:" do
        tag = io.read()
    end
    return tag
end -- parseEnd

--[[
-- When the tokens used as keys in this table are encountered during the
-- parsing of the .uss file, the method specified as a value is run on the 
-- subsequent input.
--]]
parsers = 
{
    ["title:"] = parseTitle,
    ["type:"] = parseType,
}

--[[
-- The parseType method uses this table to match tokens from the input with
-- methods that know how to extract useful information about each type.
-- ]]
typeParsers =
{
    ["1"] = parseStart,
    ["2"] = parseEnd,
    ["3"] = parseState,
    ["4"] = parseLink,
}

main = function()

    --The state machine will be constructed in this global table.
    g_stateMachine = {}

    local activeParser = nil
   
    -- read all input from stdin.

if arg[1] then
    io.input( arg[1] )
--    print("reading from "..arg[1])
end

    line = io.read()
    while true do
        -- quit when io.read() sets line to nil
        if not line then break end
        -- if there is a special parser method registered for the token 
        -- in line, set that method as the active parser.
        if parsers[line] then
            activeParser = parsers[line]
        end
       
        -- If there is an active parser method, use it to parse the current 
        -- line.  The parser will continue extracting information from the 
        -- input until it no longer understands the input.  At that time it 
        -- will return.
        if activeParser then
            local done
            done, line = activeParser(line)
            if done then
                activeParser= nil
            end
        else
            line = io.read()
        end
        
    end
    
    print("--[[")
    print(g_stateMachine.title .. " Finite State Machine")
    print()
    print("Generated from: " .. (arg[1] or "stdin"))
    if not arg[1] then
        print("\t\t\t** Consider not generating this file by piping input to ")
        print("\t\t\t** the generation script on stdin.  Naming the input file ")
        print("\t\t\t** on the generation script's command line allows these ")
        print("\t\t\t** comments to reflect the assets that involved in the ")
        print("\t\t\t** pipeline.")
    end
    if string.find(tostring(os.getenv("OS")), "Windows") == 1 then
        print("Generated by: " .. tostring(os.getenv("USERNAME")) .. 
        " on " .. tostring(os.getenv("USERDOMAIN")))
    else
        print("Generated by: " .. tostring(os.getenv("USER")) .. 
        " on " .. tostring(os.getenv("HOSTNAME")))
    end
    print("Generated at: " .. os.date())
    print()
    print("** DO NOT EDIT THIS FILE **")
    print("This file is automatically generated by the script " .. tostring(arg[0]))
    print("Please edit the datafile '" .. (arg[1] or "stdin") .. "' and rerun the conversion script")
    print("otherwise your edits to this file will be lost when this file is regenerated.")
    print(" ------------------------------]]")
    print()
    print("require \"FSM\"")
    print()
    print("-----------------------------------------------------------------------------\n" ..
          "-- Declare Namespace\n" ..
          "-----------------------------------------------------------------------------")
    print(string.format("%s = %s or {}", g_stateMachine.title, g_stateMachine.title))
    print()
    print("-----------------------------------------------------------------------------\n" ..
          "-- Declare Machine\n" ..
          "-----------------------------------------------------------------------------")
    local machineName = string.format("%s.ProtoMachine", g_stateMachine.title)
    print(string.format("%s = Machine:new{_name = \"%sProtoMachine\"}", machineName, g_stateMachine.title))
    print()
    print("-----------------------------------------------------------------------------\n" ..
          "-- Declare Events\n" ..
          "-----------------------------------------------------------------------------")
    local eventIdMap = {}
    for eventId, event in pairs(g_stateMachine.links) do
        if event.name and #event.name > 0 then
            local eventName = string.format("%sEvent", event.name)
            local found = false
            for eventId, eventName1 in pairs(eventIdMap) do
                if eventName1 == eventName then
                    found = true
                end
            end
            if not found then
                --print(string.format("%s = Event:new{_name = %q, _id = %s}", eventName, event.name, eventId))
                print(string.format("%s.%s = Event:new{_name = %q}", g_stateMachine.title, eventName, event.name, eventId))
            end
            eventIdMap[eventId] = eventName
        end
    end
    print()
    print("-----------------------------------------------------------------------------\n" ..
          "-- Declare States\n" ..
          "-----------------------------------------------------------------------------")
    local stateIdMap = {}
    for stateId, state in pairs(g_stateMachine.states) do
        if state.name and #state.name > 0 then
            local stateName = string.format("%sState", state.name)
            print(string.format("local %s = State:new{_name = %q}", stateName, state.name))
            print(string.format("%s:addState(%s)", machineName, stateName)) 
            stateIdMap[stateId] = stateName
        end
    end
    print()
    print("-----------------------------------------------------------------------------\n" ..
          "-- Setup Transitions\n" ..
          "-----------------------------------------------------------------------------")
    for stateId, stateName in pairs(stateIdMap) do
        for eventId, eventName in pairs(eventIdMap) do
            local event = g_stateMachine.links[eventId]
            if event.startId == stateId then
                print(string.format("%s:addTransition(%s.%s, %s)", stateIdMap[event.startId], 
                    g_stateMachine.title,eventName, stateIdMap[event.endId]))  
            end            
        end
    end
    print()
    print("-----------------------------------------------------------------------------\n" ..
          "-- Setup Initial State\n" ..
          "-----------------------------------------------------------------------------")
    print(string.format("%s:setInitialState(%s)", machineName, stateIdMap[g_stateMachine.links[g_stateMachine.startLink].endId]))
    print()
    print( "return " .. g_stateMachine.title )
end -- main

--begin executable

main()

--end